# coding = utf-8
import re
from time import sleep

from selenium.webdriver.remote.webelement import WebElement as WebElement_O
from selenium.webdriver.common.action_chains import ActionChains
from selenium.common.exceptions import StaleElementReferenceException, ElementNotVisibleException
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.by import By

from ..exceptions import SeleniumError
from ...report.log import logger

try:
    from StringIO import StringIO as IOStream
except ImportError:  # 3+
    from io import BytesIO as IOStream

from qturboframe.lib.utils.CustomError.custom_error import CustomError


class WebElement(WebElement_O):

    _web_element_stale_flag = True

    def get_all_frame(self):
        '''
        获取当前元素节点下的所有frame、iframe

        :Returns:
            frame\iframe元素的集合

        :Example:
         - 例: frame_list = div.get_all_frame()
        '''
        framelist = self.find_elements_by_tag_name('frame')
        iframelist = self.find_elements_by_tag_name('iframe')
        framelist.extend(iframelist)
        return framelist

    def find_element_by_attribute(self, by, value, assist='', timeout=5):
        '''
        根据多属性查找当前元素节点下的元素，
        主属性包括by、value两个参数，by对应八种元素查找方式，assist指的是辅助属性，
        即在八种元素查找方式的基础上再加上其他属性条件进行高精度的查找；
        最终结果是一个页面上的可用元素

        :Args:
         - by: 与find_element()的by参数相同
         - value: 与find_element()的value参数相同
         - assist: 辅助属性，'属性名称:属性值'，支持多组属性，多组之间用逗号隔开
         - timeout: 查找元素的超时时间，在timeout时间内会不断的查找直到找到为止，超过timeout时间则不再继续查找

        :Returns:
            返回页面上的一个可用元素，WebElement类型

        :Raises:
            SeleniumError: 未找到元素或者找到多个元素

        :Example:
         - 例: ele = element.find_element_by_attribute(By.CSS_SELECTOR, 'input#su', 'value:百度一下', 3)
        '''
        try:
            elements = self.find_elements_by_attribute(by, value, assist, timeout)
            if not elements:
                raise SeleniumError(CustomError.Error_EQ03001000_Cannot_Find_Element,by + ',' + value + ',' + assist + '属性对象没有找到!')
            lennumber = len(elements)
            if lennumber == 1:
                if not elements[0].is_displayed():
                    try:
                        elements[0].location_once_scrolled_into_view()
                    except:
                        pass
                self._web_element_stale_flag = True
                return elements[0]
            elif lennumber > 1:
                raise SeleniumError(CustomError.Error_EQ03001006_Find_More,by + ',' + value + ',' + assist + '属性对象找到多个，请确认!')
        except StaleElementReferenceException as msg:
            if self._web_element_stale_flag:
                logger.debug(str(msg) + '\n元素过时，需要重新获取！')
                self._web_element_stale_flag = False
                return self.find_element_by_attribute(by, value, assist, timeout)
            else:
                self._web_element_stale_flag = True
                raise StaleElementReferenceException(str(msg))

    def find_elements_by_attribute(self, by, value, assist='', timeout=5):
        '''
        根据多属性查找当前元素节点下符合查找条件的元素集合，
        主属性包括by、value两个参数，by对应八种元素查找方式，assist指的是辅助属性，
        即在八种元素查找方式的基础上再加上其他属性条件进行高精度的查找；
        最终结果是一个页面上的可用元素

        :Args:
         - by: 与find_element()的by参数相同
         - value: 与find_element()的value参数相同
         - assist: 辅助属性，'属性名称:属性值'，支持多组属性，多组之间用逗号隔开
         - timeout: 查找元素的超时时间，在timeout时间内会不断的查找直到找到为止，超过timeout时间则不再继续查找

        :Returns:
            返回符合条件的元素集合，[WebElement]类型

        :Raises:
            SeleniumError: 未找到元素或者当前元素节点被刷新

        :Example:
         - 例: ele_list = element.find_elements_by_attribute(By.CSS_SELECTOR, 'input#su', 'value:百度一下', 3)
        '''
        try:
            self.is_displayed()
        except StaleElementReferenceException as msg:
            raise SeleniumError(CustomError.Error_EQ03001007_Element_Was_Refreshed,'父级元素被刷新：' + str(msg))
        elements = list()
        timecount = 0
        maxcount = timeout * 10
        while timecount < maxcount:
            times = 0
            while times < 5:
                try:
                    elements = self.find_elements(by, value)
                    # 根据辅助属性进行元素筛选
                    if elements and assist:
                        # 根据辅助属性，判断元素的属性值是否对应，找出筛检对象，
                        assistlist = assist.split(sep=',')
                        for e in assistlist:
                            indexnumber = e.index(':')
                            i = 0
                            # 从主属性对象集合中找出附属属性
                            while i < len(elements):
                                if e[0:indexnumber] == 'text':
                                    temp = e[indexnumber + 1:]
                                    # 因为用正则表达式进行文本匹配，文本可以含有正则表达式的特殊字符，所以把特殊字符进行转义
                                    re_special_character = {'.': '\.'
                                        , '*': '\*'
                                        , '(': '\('
                                        , ')': '\)'
                                        , '$': '\$'
                                        , '+': '\+'
                                        , '[': '\['
                                        , ']': '\]'
                                        , '?': '\?'
                                        , '{': '\{'
                                        , '|': '\|'}
                                    for key, value in re_special_character.items():
                                        temp = temp.replace(key, value)
                                    re_str = '^' + temp + '$'
                                    if e[indexnumber + 1:].startswith('.'):
                                        re_str = re_str.strip('^')
                                    if e[indexnumber + 1:].endswith('.*'):
                                        re_str = re_str.strip('$')
                                    # 有时当元素在屏幕上直接不可见时，用text获取到的文本为空
                                    if not (re.search(re_str, elements[i].text.strip()) or re.search(re_str,elements[i].get_attribute("innerText").strip())):
                                        elements.remove(elements[i])
                                    else:
                                        i += 1
                                else:
                                    re_special_character = {'.': '\.'
                                        , '*': '\*'
                                        , '(': '\('
                                        , ')': '\)'
                                        , '$': '\$'
                                        , '+': '\+'
                                        , '[': '\['
                                        , ']': '\]'
                                        , '?': '\?'
                                        , '{': '\{'
                                        , '|': '\|'}
                                    element_attr = elements[i].get_attribute(e[0:indexnumber])
                                    re_word = e[indexnumber + 1:]
                                    for key, value in re_special_character.items():
                                        re_word = re_word.replace(key, value)

                                    # 正则表达式中需要做设定，必须以给定的字符串打头和结尾
                                    if not (element_attr and (
                                            re.search('^' + re_word + '$', element_attr) or
                                            re.search('^' + re_word + '\s', element_attr) or
                                            re.search('\s' + re_word + '$', element_attr) or
                                            re.search('\s' + re_word + '\s', element_attr))):
                                        elements.remove(elements[i])
                                    else:
                                        i += 1
                    break
                # 增加异常StaleElementReferenceException抓捕，防止出现对象刷新导致的对象获取失效
                except StaleElementReferenceException as msg:
                    logger.debug(str(msg) + '\n等待元素刷新！')
                    sleep(0.2)
                    times += 1
                except NoSuchElementException as msg:
                    logger.debug(str(msg))
                    elements = []
                    break
                except Exception as msg:
                    raise SeleniumError(CustomError.Error_EQ03001000_Cannot_Find_Element,by + ',' + value + '属性对象查找出错：' + str(msg))
            else:
                raise SeleniumError(CustomError.Error_EQ03001008_Refresh_Timeout,'元素刷新超时！')
            timecount = timecount + 1
            if not elements and timecount < maxcount:
                sleep(0.1)
            else:
                return elements
        else:
            return elements

    def exist(self, by, value, assist='', timeout=5):
        '''
        根据多属性判断当前节点下符合条件的元素是否存在，
        主属性包括by、value两个参数，by对应八种元素查找方式，assist指的是辅助属性，
        即在八种元素查找方式的基础上再加上其他属性条件进行高精度的查找；
        最终结果是一个页面上的可用元素

        :Args:
         - by: 与find_element()的by参数相同
         - value: 与find_element()的value参数相同
         - assist: 辅助属性，'属性名称:属性值'，支持多组属性，多组之间用逗号隔开
         - timeout: 查找元素的超时时间，在timeout时间内会不断的查找直到找到为止，超过timeout时间则不再继续查找

        :Returns:
            存在则返回页面上的一个可用元素，WebElement类型；不存在则返回None

        :Example:
         - 例: ele = element.exist(By.CSS_SELECTOR, 'input#su', 'value:百度一下', 3)
        '''

        try:
            findresult = self.find_element_by_attribute(by, value, assist, timeout)
        except SeleniumError:
            findresult = None
        return findresult

    def find_first_displayed_element(self, by, value, timeout=5):
        elements = self.find_elements_by_attribute(by, value, timeout=timeout)
        for element in elements:
            try:
                # 使元素滚动到可见位置
                element.location_once_scrolled_into_view
            except:
                pass
            if element.is_displayed():
                return element
        return None

    def find_element_contains_text(self, by, value, text, timeout=5):
        elements = self.find_elements(by, value)
        for element in elements:
            try:
                #使元素滚动到可见位置
                element.location_once_scrolled_into_view
            except:
                pass
            if element.is_displayed() and text in element.get_attribute("innerText"):
                return element
        return None

    def find_displayed_elements(self, by, value, timeout=5):
        elements = self.find_elements_by_attribute(by, value, timeout=timeout)
        element_list = []
        for element in elements:
            try:
                #使其滚动到页面可见位置
                elements[0].location_once_scrolled_into_view
            except:
                pass
            if element.is_displayed():
                element_list.append(element)
        return element_list

    def is_readonly(self):
        """
        判断元素是否是只读状态

        :Returns:
            只读则返回True，非只读则返回False

        :Example:
         - 例: element.is_readonly()
        """
        findresult = self.get_attribute('readonly')
        if findresult is None:
            return False
        else:
            return True

    def focus(self, position='top'):
        '''
        聚焦到当前元素

        :Args:
         - position: top表示聚焦显示到顶部，bottom表示聚焦显示到底部

        :Example:
         - 例1: element.focus()
         - 例2: element.focus('bottom')
        '''
        if position.lower() == 'top':
            self._parent.execute_script("arguments[0].scrollIntoView();", self)
        elif position.lower() == 'bottom':
            self._parent.execute_script("arguments[0].scrollIntoView(false);", self)
        else:
            raise SeleniumError('传入的position参数值不正确！')

    def double_click(self):
        '''
        双击元素

        :Example:
         - 例: element.double_click()
        '''
        ActionChains(self._parent).double_click(self).perform()

    def left_click(self):
        '''
        单击元素

        :Example:
         - 例: element.left_click()
        '''
        ActionChains(self._parent).click(self).perform()

    def right_click(self):
        '''
        元素右键

        :Example:
         - 例: element.right_click()
        '''
        ActionChains(self._parent).context_click(self).perform()

    def move_to(self):
        '''
        鼠标移至元素所在位置悬浮

        :Example:
         - 例: element.move_to()
        '''
        ActionChains(self._parent).move_to_element(self).perform()

    def secure_click(self, timeout=5):
        '''
        元素点击，等待页面刷新完成后再进行点击

        :Args:
         - timeout: 等待页面刷新完成的超时时间

        :Raises:
            SeleniumError: 元素不可点击

        :Example:
         - 例: button.secure_click()
        '''
        timmes = 0
        while timmes < timeout * 20:
            try:
                self.click()
                break
            except ElementNotVisibleException as msg:
                sleep(0.05)
                timmes += 1
                logger.debug('等待元素可见！')
                self.focus()
            except Exception as msg:
                # 处理可能出现的元素不在界面显示，无法点击的问题，
                # 将使用滚动条切换到可显示指定元素的位置，然后再做一次点击操作
                exception_str = '{0}'.format(msg)
                if 'is not clickable at point' in exception_str:
                    sleep(0.05)
                    timmes += 1
                    logger.debug('等待元素可点击！')
                    self.focus()
                else:
                    logger.debug(msg)
                    raise msg
        else:
            logger.debug('元素不可点击！')
            raise SeleniumError(CustomError.Error_EQ03001009_Cannot_Operation,'元素不可点击！')

    def find_element_by_id(self, id_):
        """Finds element within this element's children by ID.

        :Args:
         - id\_ - ID of child element to locate.

        :Returns:
         - WebElement - the element if it was found

        :Raises:
         - NoSuchElementException - if the element wasn't found

        :Usage:
            foo_element = element.find_element_by_id('foo')
        """
        return WebElement_O.find_element_by_id(self, id_)

    def find_elements_by_id(self, id_):
        """Finds a list of elements within this element's children by ID.
        Will return a list of webelements if found, or an empty list if not.

        :Args:
         - id\_ - Id of child element to find.

        :Returns:
         - list of WebElement - a list with elements if any was found.  An
           empty list if not

        :Usage:
            elements = element.find_elements_by_id('foo')
        """
        return WebElement_O.find_elements_by_id(self, id_)

    def find_element_by_name(self, name):
        """Finds element within this element's children by name.

        :Args:
         - name - name property of the element to find.

        :Returns:
         - WebElement - the element if it was found

        :Raises:
         - NoSuchElementException - if the element wasn't found

        :Usage:
            element = element.find_element_by_name('foo')
        """
        return WebElement_O.find_element_by_name(self, name)

    def find_elements_by_name(self, name):
        """Finds a list of elements within this element's children by name.

        :Args:
         - name - name property to search for.

        :Returns:
         - list of webelement - a list with elements if any was found.  an
           empty list if not

        :Usage:
            elements = element.find_elements_by_name('foo')
        """
        return WebElement_O.find_elements_by_name(self, name)

    def find_element_by_link_text(self, link_text):
        """Finds element within this element's children by visible link text.

        :Args:
         - link_text - Link text string to search for.

        :Returns:
         - WebElement - the element if it was found

        :Raises:
         - NoSuchElementException - if the element wasn't found

        :Usage:
            element = element.find_element_by_link_text('Sign In')
        """
        return WebElement_O.find_element_by_link_text(self, link_text)

    def find_elements_by_link_text(self, link_text):
        """Finds a list of elements within this element's children by visible link text.

        :Args:
         - link_text - Link text string to search for.

        :Returns:
         - list of webelement - a list with elements if any was found.  an
           empty list if not

        :Usage:
            elements = element.find_elements_by_link_text('Sign In')
        """
        return WebElement_O.find_elements_by_link_text(self, link_text)

    def find_element_by_partial_link_text(self, link_text):
        """Finds element within this element's children by partially visible link text.

        :Args:
         - link_text: The text of the element to partially match on.

        :Returns:
         - WebElement - the element if it was found

        :Raises:
         - NoSuchElementException - if the element wasn't found

        :Usage:
            element = element.find_element_by_partial_link_text('Sign')
        """
        return WebElement_O.find_element_by_partial_link_text(self, link_text)

    def find_elements_by_partial_link_text(self, link_text):
        """Finds a list of elements within this element's children by link text.

        :Args:
         - link_text: The text of the element to partial match on.

        :Returns:
         - list of webelement - a list with elements if any was found.  an
           empty list if not

        :Usage:
            elements = element.find_elements_by_partial_link_text('Sign')
        """
        return WebElement_O.find_elements_by_partial_link_text(self, link_text)

    def find_element_by_tag_name(self, name):
        """Finds element within this element's children by tag name.

        :Args:
         - name - name of html tag (eg: h1, a, span)

        :Returns:
         - WebElement - the element if it was found

        :Raises:
         - NoSuchElementException - if the element wasn't found

        :Usage:
            element = element.find_element_by_tag_name('h1')
        """
        return WebElement_O.find_element_by_tag_name(self, name)

    def find_elements_by_tag_name(self, name):
        """Finds a list of elements within this element's children by tag name.

        :Args:
         - name - name of html tag (eg: h1, a, span)

        :Returns:
         - list of WebElement - a list with elements if any was found.  An
           empty list if not

        :Usage:
            elements = element.find_elements_by_tag_name('h1')
        """
        return WebElement_O.find_elements_by_tag_name(self, name)

    def find_element_by_xpath(self, xpath):
        """Finds element by xpath.

        :Args:
         - xpath - xpath of element to locate.  "//input[@class='myelement']"

        Note: The base path will be relative to this element's location.

        This will select the first link under this element.

        ::

            myelement.find_element_by_xpath(".//a")

        However, this will select the first link on the page.

        ::

            myelement.find_element_by_xpath("//a")

        :Returns:
         - WebElement - the element if it was found

        :Raises:
         - NoSuchElementException - if the element wasn't found

        :Usage:
            element = element.find_element_by_xpath('//div/td[1]')
        """
        return WebElement_O.find_element_by_xpath(self, xpath)

    def find_elements_by_xpath(self, xpath):
        """Finds elements within the element by xpath.

        :Args:
         - xpath - xpath locator string.

        Note: The base path will be relative to this element's location.

        This will select all links under this element.

        ::

            myelement.find_elements_by_xpath(".//a")

        However, this will select all links in the page itself.

        ::

            myelement.find_elements_by_xpath("//a")

        :Returns:
         - list of WebElement - a list with elements if any was found.  An
           empty list if not

        :Usage:
            elements = element.find_elements_by_xpath("//div[contains(@class, 'foo')]")

        """
        return WebElement_O.find_elements_by_xpath(self, xpath)

    def find_element_by_class_name(self, name):
        """Finds element within this element's children by class name.

        :Args:
         - name: The class name of the element to find.

        :Returns:
         - WebElement - the element if it was found

        :Raises:
         - NoSuchElementException - if the element wasn't found

        :Usage:
            element = element.find_element_by_class_name('foo')
        """
        return WebElement_O.find_element_by_class_name(self, name)

    def find_elements_by_class_name(self, name):
        """Finds a list of elements within this element's children by class name.

        :Args:
         - name: The class name of the elements to find.

        :Returns:
         - list of WebElement - a list with elements if any was found.  An
           empty list if not

        :Usage:
            elements = element.find_elements_by_class_name('foo')
        """
        return WebElement_O.find_elements_by_class_name(self, name)

    def find_element_by_css_selector(self, css_selector):
        """Finds element within this element's children by CSS selector.

        :Args:
         - css_selector - CSS selector string, ex: 'a.nav#home'

        :Returns:
         - WebElement - the element if it was found

        :Raises:
         - NoSuchElementException - if the element wasn't found

        :Usage:
            element = element.find_element_by_css_selector('#foo')
        """
        return WebElement_O.find_element_by_css_selector(self, css_selector)

    def find_elements_by_css_selector(self, css_selector):
        """Finds a list of elements within this element's children by CSS selector.

        :Args:
         - css_selector - CSS selector string, ex: 'a.nav#home'

        :Returns:
         - list of WebElement - a list with elements if any was found.  An
           empty list if not

        :Usage:
            elements = element.find_elements_by_css_selector('.foo')
        """
        return WebElement_O.find_elements_by_css_selector(self, css_selector)

    def find_element(self, by=By.ID, value=None):
        """
        'Private' method used by the find_element_by_* methods.

        :Usage:
            Use the corresponding find_element_by_* instead of this.

        :rtype: WebElement
        """

        return WebElement_O.find_element(self, by, value)

    def find_elements(self, by=By.ID, value=None):
        """
        'Private' method used by the find_elements_by_* methods.

        :Usage:
            Use the corresponding find_elements_by_* instead of this.

        :rtype: list of WebElement
        """

        return WebElement_O.find_elements(self, by, value)

